part of '../fhare.dart';

final RegExp RegexDigits = RegExp(r'[0-9]+');
final RegExp RegexInteger = RegExp(r'[-+0-9]+');
final RegExp RegexReal = RegExp(r'[-+0-9.]+');

class Regs {
  Regs._();

  static final RegExp digits = RegExp(r'[0-9]+');
  static final RegExp integers = RegExp(r'[-+0-9]+');
  static final RegExp reals = RegExp(r'[-+0-9.]+');
  static final RegExp realsUnsigned = RegExp(r'[0-9.]+');
  static final RegExp newLines = RegExp(r'\r\n|\n|\r');
}

typedef FunctionVoid = void Function();
typedef FunctionP<P> = void Function(P);
typedef FunctionR<R> = R Function();
typedef FunctionRP<R, P> = R Function(P);

typedef Predicate<T> = bool Function(T);

typedef ReturnValue<R> = R Function();
typedef OnValue<T> = void Function(T value);

typedef OnWidget<T> = Widget Function(T value);
typedef OnItemView<T> = Widget Function(T value);
typedef OnWidgetOpt<T> = Widget? Function(T value);

typedef OnLabel<T> = String Function(T value);
typedef OnChildren<T> = List<T> Function(T value);
typedef BoolCallback = bool Function();
typedef TextCallback = void Function(String text);
typedef StringCallback = void Function(String text);
typedef NumCallback = void Function(num value);
typedef IntCallback = void Function(int value);
typedef DoubleCallback = void Function(double value);

typedef ListString = List<String>;
typedef ListInt = List<int>;
typedef ListWidget = List<Widget>;

typedef TypeWidgetBuilder<T> = T Function(BuildContext context);

typedef AnyPropMap = Map<String, dynamic>;

Future<PackageInfo> packageInfo() async {
  PackageInfo info = await PackageInfo.fromPlatform();
  return info;
}

class DelayCall {
  final int _delayMills;
  final VoidCallback _callback;
  int _triggerTime = 0;

  DelayCall({required void Function() callback, int delayMills = 500})
      : _callback = callback,
        _delayMills = delayMills;

  void trigger() {
    _triggerTime = DateTime.now().millisecondsSinceEpoch;
    Future.delayed(Duration(milliseconds: _delayMills), _invoke);
  }

  void _invoke() {
    int now = DateTime.now().millisecondsSinceEpoch;
    if (now >= _triggerTime + _delayMills) {
      _callback();
    }
  }
}

class AnyProp<T> {
  final AnyPropMap _map;
  final String key;
  final T? missValue;

  AnyProp({required AnyPropMap map, required this.key, this.missValue}) : _map = map;

  T get value {
    return _map[key] ?? missValue;
  }

  set value(T newValue) {
    if (newValue == null) {
      _map.remove(key);
    } else {
      _map[key] = newValue;
    }
  }

  bool get exists => _map.containsKey(key);

  T? remove() {
    return _map.remove(key);
  }

  @override
  String toString() {
    return "${this.runtimeType}{ key=$key, value=$value }";
  }
}

class Rand {
  static int next(int minVal, int maxVal) {
    return math.Random().nextInt(maxVal - minVal) + minVal;
  }
}

extension ObjectIdentyEx on Object {
  bool identyEqual(Object? other) {
    return identical(this, other);
  }
}

class PropValue {
  final String prop;
  final String value;

  PropValue({required this.prop, required this.value});

  int? get intValue => value.toInt;

  bool? get boolValue {
    if (value == "true" || value == "1") return true;
    if (value == "false" || value == "0") return false;
    return null;
  }

  @override
  String toString() {
    return "$prop=$value";
  }
}

final RegExp _itemSep = RegExp(r"[,;]");
final RegExp _attrSep = RegExp(r"[=:]");
//a=1;b:2,c:3   => [(a,1), (b,2), (c, 3)]
List<PropValue> parseProperties(String text, {Pattern? itemSep, Pattern? attrSep}) {
  List<String> items = text.split(itemSep ?? _itemSep).mapList((e) => e.trim()).filter((e) => e.isNotEmpty);
  List<PropValue> values = [];
  for (String item in items) {
    List<String> pair = item.split(attrSep ?? _attrSep).mapList((e) => e.trim());
    if (pair.isEmpty) continue;
    values << PropValue(prop: pair.first, value: pair.second?.trim() ?? "");
  }
  return values;
}

extension CastToExt on Object {
  T? castTo<T>() {
    if (this is T) return this as T;
    return null;
  }
}

extension UriAppendArgumentsExt on Uri {
  Uri appendedParams(Map<String, String> args) {
    Uri uri = this;
    LinkedHashMap<String, List<String>> newMap = LinkedHashMap<String, List<String>>.from(uri.queryParametersAll);
    for (var p in args.entries) {
      List<String> ls = newMap[p.key] ?? [];
      ls.add(p.value);
      newMap[p.key] = ls;
    }
    return uri.replace(queryParameters: newMap);
  }
}

typedef ProgressCallback = void Function(int total, int current);

class ProgressPublish {
  ProgressCallback? onProgress;
  final int total;
  int current = 0;
  int preTime = 0;
  final int delay; //mill sec

  ProgressPublish(this.total, this.onProgress, {int? delayMS}) : delay = delayMS ?? 100 {
    assert(delay > 0);
  }

  void add(int count) {
    bool fire = preTime == 0;
    current += count;
    fire = fire || total == current;
    int currTime = DateTime.now().millisecondsSinceEpoch;
    fire = fire || (currTime - preTime > delay);
    if (fire) {
      preTime = currTime;
      onProgress?.call(total, current);
    }
  }
}

extension StreamReadBytesExt on Stream<List<int>> {
  Stream<List<int>> progress(int total, ProgressCallback? onProgress, {int? delayMS}) {
    ProgressPublish pp = ProgressPublish(total, onProgress, delayMS: delayMS);
    return transform(StreamTransformer.fromHandlers(handleData: (List<int> data, EventSink<List<int>> sink) {
      pp.add(data.length);
      sink.add(data);
    }, handleDone: (EventSink<List<int>> sink) {
      sink.close();
    }, handleError: (err, st, sink) {
      sink.addError(err, st);
    }));
  }

  Future<Uint8List> allBytes() async {
    var completer = Completer<Uint8List>();
    var sink = ByteConversionSink.withCallback((bytes) => completer.complete(Uint8List.fromList(bytes)));
    listen((data) {
      sink.add(data);
    }, onError: completer.completeError, onDone: sink.close, cancelOnError: true);
    return completer.future;
  }

  Future<String> allText([Encoding encoding = utf8]) => encoding.decodeStream(this);
}

extension LabelValueStringExt on String {
  // LabelValue<T> valued<T>(T value) {
  //   return LabelValue<T>(this, value);
  // }

  LabelValue<T> val<T>(T value) {
    return LabelValue<T>(this, value);
  }
}

extension StringKeyValue<V> on String {
  LabelValue<V> operator >>(V value) {
    return LabelValue<V>(this, value);
  }
}

extension SymbolKeyValue<V> on Symbol {
  LabelValue<V> operator >>(V value) {
    return LabelValue<V>(stringValue, value);
  }
}

extension ListStringPairEx<T> on List<LabelValue<T>> {
  LinkedHashMap<String, T> toMap() {
    List<MapEntry<String, T>> meList = mapList((e) => MapEntry<String, T>(e.label, e.value));
    return LinkedHashMap<String, T>.fromEntries(meList);
  }
}

typedef LabelInt = LabelValue<int>;

class LabelValue<T> {
  final String label;
  final T value;

  const LabelValue(this.label, this.value);

  @override
  bool operator ==(Object other) {
    return other is LabelValue<T> && label == other.label && value == other.value;
  }

  @override
  int get hashCode => label.hashCode + value.hashCode;

  @override
  String toString() {
    return "LabelValue{label:$label, value:$value}";
  }
}

extension LetBlock<T> on T {
  R let<R>(R Function(T e) block) => block(this);

  // R run<R>(R Function(T e) block) => block(this);
  //
  // T apply(void Function(T e) block) {
  //   block(this);
  //   return this;
  // }

  T also(void Function(T) block) {
    block(this);
    return this;
  }
}

class ResultException {
  String message;
  int code;

  ResultException(this.message, this.code);
}

Future<void> postAction(FutureOr<void> Function() callback) async {
  await Future.delayed(Duration(milliseconds: 0), callback);
}

Future<void> delayMills(int millSeconds, [FutureOr<void> Function()? callback]) async {
  await Future.delayed(Duration(milliseconds: millSeconds), callback);
}

Future<void> delayMillSeconds(int millSeconds, [FutureOr<void> Function()? callback]) async {
  await Future.delayed(Duration(milliseconds: millSeconds), callback);
}

extension ObjectStringKey on Object {
  String get stringKey {
    if (this is String) return this as String;
    if (this is Symbol) return (this as Symbol).stringValue;
    fatal("NOT a String OR Symbol");
  }
}

//
// 0 A single digit
// # A single digit, omitted if the value is zero
//     . Decimal separator
// - Minus sign
// , Grouping separator
// E Separates mantissa and expontent
// + - Before an exponent, to say it should be prefixed with a plus sign.
// % - In prefix or suffix, multiply by 100 and show as percentage
// ‰ (\u2030) In prefix or suffix, multiply by 1000 and show as per mille
// ¤ (\u00A4) Currency sign, replaced by currency name
// ' Used to quote special characters
// ; Used to separate the positive and negative patterns (if both present)
extension NumFormatExt on num {
  String formated(String f) {
    return intl.NumberFormat(f).format(this);
  }
}

Never fatal(String? msg) {
  throw Exception(msg);
}

Never error(String? msg) {
  throw Exception(msg);
}

class Plat {
  Plat._();

  static bool get isDebug => kDebugMode;

  static bool get isDebugMode => kDebugMode;

  static bool get isWeb => kIsWeb || kIsWasm;

  static bool get isMobile => !isWeb && (Platform.isAndroid || Platform.isIOS);

  static bool get isDesktop => !isWeb && (Platform.isLinux || Platform.isWindows || Platform.isMacOS);

  static bool get isAndroid => !isWeb && (Platform.isAndroid);

  static bool get isIOS => !isWeb && (Platform.isIOS);

  static bool get isFuchsia => !isWeb && (Platform.isFuchsia);

  static bool get isMacOS => !isWeb && (Platform.isMacOS);

  static bool get isWindows => !isWeb && (Platform.isWindows);

  static bool get isLinux => !isWeb && (Platform.isLinux);
}

void setClipboardText(String text) {
  Clipboard.setData(ClipboardData(text: text));
}

Future<String?> getClipboardText() async {
  ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
  return data?.text;
}

FunctionVoid? bindOne<T>(T value, FunctionP<T>? callback) {
  if (callback == null) return null;
  return OneBinder<T>(value, callback).call;
}

void Function(B)? bindFirst<A, B>(A value, void Function(A, B)? callback) {
  if (callback == null) return null;
  return TwoBinder<A, B>(callback).bindFirst(value);
}

void Function(A)? bindSecond<A, B>(B value, void Function(A, B)? callback) {
  if (callback == null) return null;
  return TwoBinder<A, B>(callback).bindSecond(value);
}

VoidCallback? bindBoth<A, B>(A first, B second, void Function(A, B)? callback) {
  if (callback == null) return null;
  return TwoBinder<A, B>(callback).bindBoth(first, second);
}

class OneBinder<A> {
  A value;
  FunctionP<A> callback;

  OneBinder(this.value, this.callback);

  void call() {
    callback(value);
  }
}

class TwoBinder<A, B> {
  late A firstValue;
  late B secondValue;
  void Function(A, B) callback;

  TwoBinder(this.callback);

  void Function(B) bindFirst(A value) {
    firstValue = value;
    return callB;
  }

  void Function(A) bindSecond(B value) {
    secondValue = value;
    return callA;
  }

  VoidCallback bindBoth(A first, B second) {
    firstValue = first;
    secondValue = second;
    return call;
  }

  void call() {
    callback(firstValue, secondValue);
  }

  void callA(A value) {
    callback(value, secondValue);
  }

  void callB(B value) {
    callback(firstValue, value);
  }
}

FunctionP<T> unbindOne<T>(VoidCallback action) {
  return UnbindOne<T>(action).call;
}

class UnbindOne<T> {
  VoidCallback action;

  UnbindOne(this.action);

  void call(T arg) {
    action();
  }
}

class HareError implements Exception {
  final dynamic message;

  HareError([this.message]);

  @override
  String toString() {
    Object? message = this.message;
    if (message == null) return "HareError";
    return "HareError: $message";
  }
}
